import SEO from "../components/SEO";
import { TOC, TOCList, TOCLink } from "../components/TOC";
import { Pipe } from "../components/Pipe";

<SEO
	title="Checkbox"
	description="Accessible components to build custom, tri-state checkboxes in React"
/>

# Checkbox

<TOC>
	<TOCList>
		<TOCLink href="#mixedcheckbox-1">MixedCheckbox</TOCLink>
		<TOCLink href="#usemixedcheckbox">useMixedCheckbox</TOCLink>
		<TOCLink href="#customcheckbox-1">CustomCheckbox</TOCLink>
		<TOCLink href="#customcheckboxcontainer">CustomCheckboxContainer</TOCLink>
		<TOCLink href="#customcheckboxinput">CustomCheckboxInput</TOCLink>
	</TOCList>
</TOC>

- Source: https://github.com/reach/reach-ui/tree/main/packages/checkbox
- WAI-ARIA: https://www.w3.org/TR/wai-aria-practices-1.2/#checkbox

`@reach/checkbox` provides two top-level components:

- `MixedCheckbox`
- `CustomCheckbox`

## MixedCheckbox

A MixedCheckbox simply renders an HTML input element where the `type` attribute is equal to `"checked"`. Whereas the native input element technically only has two states (`true` or `false`), there is a third visual state of `indeterminate` that is designed to suggest that a user has fulfilled some part of whatever the checkbox is meant to control. For example, you may have multiple hierarchal checkboxes nested:

```md
[-] All fruits
-- [ ] Apple
-- [x] Banana
-- [x] Orange
```

In this example, the `All fruits` checkbox is in an `indeterminate` state because some (but not all) fruits in the list are checked. While this effect is possible with plain input components, the `MixedCheckbox` component makes managing/syncing its state with the correct DOM attributes much simpler. All you have to do is pass the `checked` state, and `@reach/checkbox` handles the necessary aria attributes and related node data!

A mixed checkbox is not something you can naturally toggle by simply clicking the box itself. As such, you should manage its state in your app by passing a `checked` prop and an `onChange` handler. A mixed checkbox is necessarily controlled. If you use a `MixedCheckbox` component without controlling its state, it will behave exactly the same way a native HTML input element behaves.

If you don't need `indeterminate` state, you should probably just use a native HTML input for your checkboxes. But of course, sometimes designers have some other ideas that call for a custom solution. In that case, the `CustomCheckbox` component provides a customizable wrapper element that can be styled to fit your needs.

## CustomCheckbox

A `CustomCheckbox` is useful because full control of a native HTML input's design is not always possible. You may want to provide custom check graphics or change the shape of the check or its color. This component provides a handy wrapper around a visually hidden native checkbox so that we avoid re-creating all of its native event behavior.

`CustomCheckbox` uses our `MixedCheckbox` so you get the same benefits for dealing with `indeterminate` state when you use either!

> Accessibility Note: If you use our `CustomCheckbox` component, you will still need to ensure that your styles follow the guidelines outlined in the [WAI-ARIA specifications for checkboxes](https://www.w3.org/TR/wai-aria-practices-1.2/#checkbox). Pay special attention to focus styles for keyboard navigation. Our default styles provide focus styles by default.

## Installation

From the command line in your project directory, run `npm install @reach/checkbox` or `yarn add @reach/checkbox`. Then import the components and styles that you need:

```bash
npm install @reach/checkbox
# or
yarn add @reach/checkbox
```

```js
import {
	CustomCheckbox,
	CustomCheckboxContainer,
	CustomCheckboxInput,
} from "@reach/checkbox";
import "@reach/checkbox/styles.css";
```

If you are only using `MixedCheckbox` or `useMixedCheckbox`, there is no need to include the stylesheet.

```js
import { MixedCheckbox, useMixedCheckbox } from "@reach/checkbox";
```

## Usage

### Example MixedCheckbox

```jsx
// jsx-demo
function Example() {
	const [checked, setChecked] = React.useState(true);
	return (
		<div>
			<label>
				<MixedCheckbox
					value="whatever"
					checked={checked}
					onChange={(event) => {
						setChecked(event.target.checked);
					}}
				/>
				I am feeling good today
			</label>
			<label>
				<MixedCheckbox checked="mixed" />
				Perma-mixed
			</label>
			<div style={{ marginTop: 10 }}>
				<button onClick={() => setChecked("mixed")}>
					I'm not sure how I feel!
				</button>
			</div>
		</div>
	);
}
```

### Example CustomCheckbox

With custom checkbox, you can choose between a high-level API where DOM elements
are not individually exposed as components, or use the composed API to access
each sub-component directly.

#### High-level `CustomCheckbox` API

```jsx
// jsx-demo
(() => {
	function MyCheckbox({ children, ...props }) {
		return (
			<span className={`example-custom-checkbox ${props.value}`}>
				<label>
					<CustomCheckbox {...props} />
					{children}
				</label>
			</span>
		);
	}

	function Checklist() {
		return (
			<fieldset>
				<legend>What is your favorite fruit?</legend>
				<MyCheckbox name="favorite-fruit" value="apple" color="#B22222">
					Apple <span aria-hidden>🍎</span>
				</MyCheckbox>
				<MyCheckbox name="favorite-fruit" value="orange" color="#FF8C00">
					Orange <span aria-hidden>🍊</span>
				</MyCheckbox>
				<MyCheckbox name="favorite-fruit" value="banana" color="#FFD700">
					Banana <span aria-hidden>🍌</span>
				</MyCheckbox>
			</fieldset>
		);
	}

	return <Checklist />;
})();
```

#### Composed CustomCheckbox API

```jsx
// jsx-demo
(() => {
	function Example() {
		return (
			<div>
				<label style={{ display: "flex", alignItems: "center" }}>
					<MyCheckbox value="whatever" />
					This is a pretty cool checkbox; do you agree?
				</label>
				<br />
				<label style={{ display: "flex", alignItems: "center" }}>
					<MyCheckbox checked="mixed" value="something-else" />
					I'm just an example of what I'd look like if I had a mixed state.
				</label>
			</div>
		);
	}

	function MyCheckbox(props) {
		const [checkedState, setChecked] = React.useState(props.checked || false);
		const checked = props.checked != null ? props.checked : checkedState;

		return (
			<CustomCheckboxContainer
				checked={props.checked != null ? props.checked : checked}
				onChange={(event) => setChecked(event.target.checked)}
				style={getContainerStyle()}
			>
				<CustomCheckboxInput {...props} />
				<span aria-hidden style={getCheckStyle(checked)} />
			</CustomCheckboxContainer>
		);
	}

	function getContainerStyle() {
		return {
			background: "rgba(240, 240, 250, 0.8)",
			border: "2px solid rgba(0, 0, 0, 0.8)",
			borderRadius: "3px",
			height: 26,
			width: 26,
		};
	}

	function getCheckStyle(checked) {
		return {
			display: "block",
			position: "absolute",
			width: "60%",
			height: "60%",
			top: "50%",
			left: "50%",
			transform: `translate(-50%, -50%) scaleX(${!!checked ? 1 : 0}) scaleY(${
				checked === true ? 1 : checked === "mixed" ? 0.4 : 0
			})`,
			transition: "transform 200ms ease-out, background 200ms ease-out",
			zIndex: 1,
			background:
				checked === true
					? "green"
					: checked === "mixed"
					? "goldenrod"
					: "transparent",
		};
	}

	return <Example />;
})();
```

## Component API

### MixedCheckbox

Tri-state checkbox that accepts `checked` values of `true`, `false` or `"mixed"`.

```jsx
// jsx-demo
function Example() {
	let [favoriteCondiments, setFavoriteCondiments] = React.useState({
		mayo: true,
		mustard: true,
		ketchup: false,
	});

	// We can determine if all or some of the nested checkboxes are selected and
	// use that to determine the state of our parent checkbox.
	let allCondimentsChecked = Object.keys(favoriteCondiments).every(
		(condiment) => favoriteCondiments[condiment] === true
	);
	let someCondimentsChecked = allCondimentsChecked
		? false
		: Object.keys(favoriteCondiments).some(
				(condiment) => favoriteCondiments[condiment] === true
		  );

	let parentIsChecked = allCondimentsChecked
		? true
		: someCondimentsChecked
		? "mixed"
		: false;

	// When we toggle a parent checkbox, we expect all of the nested checkboxes
	// to toggle with it.
	function handleParentChange(event) {
		setFavoriteCondiments(
			Object.keys(favoriteCondiments).reduce(
				(state, condiment) => ({
					...state,
					[condiment]: !allCondimentsChecked,
				}),
				{}
			)
		);
	}

	function handleChildChange(event) {
		let { checked, value } = event.target;
		setFavoriteCondiments({
			...favoriteCondiments,
			[value]: checked,
		});
	}

	return (
		<fieldset>
			<label>
				<MixedCheckbox
					value="condiments"
					checked={parentIsChecked}
					onChange={handleParentChange}
				/>
				{allCondimentsChecked ? "Unselect" : "Select"} all condiments
			</label>
			<fieldset style={{ margin: "1rem 0 0", padding: "1rem 1.5rem" }}>
				<legend>Condiments</legend>

				<ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
					{Object.entries(favoriteCondiments).map(([value, state]) => (
						<li key={value}>
							<label>
								<MixedCheckbox
									name="condiment"
									value={value}
									checked={state}
									onChange={handleChildChange}
								/>
								{value}
							</label>
						</li>
					))}
				</ul>
			</fieldset>
		</fieldset>
	);
}
```

#### MixedCheckbox Props

`MixedCheckbox` inherits its props from the `React.ComponentProps<'input'>` type, with additional context documented below.

[Check out the React documentation for additional information about form inputs in React.](https://reactjs.org/docs/forms.html)

| Prop                                  | Type                         | Required |
| ------------------------------------- | ---------------------------- | -------- |
| [`checked`](#mixedcheckbox-checked)   | `boolean` <Pipe /> `"mixed"` | false    |
| [`onChange`](#mixedcheckbox-onchange) | `func`                       | false    |

##### MixedCheckbox `checked`

`checked?: boolean | "mixed`

Whether or not the checkbox is checked or in a `mixed` (indeterminate) state.

##### MixedCheckbox `onChange`

`onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;`

The callback that is fired when the checkbox value is changed.

### `useMixedCheckbox`

A hook that can be used to turn any HTML input component into a tri-state checkbox.

You must create a ref and pass it along with additional arguments to return a props object and state-related data.

```jsx
// jsx-demo
function Example({ disabled = false }) {
	const [checked, setChecked] = React.useState(true);
	let inputRef = React.useRef(null);
	let [inputProps, stateData] = useMixedCheckbox(inputRef, {
		// boolean
		// A mixed checkbox may receive either `defaultChecked` or `checked`
		// values, but not both!
		defaultChecked: undefined,
		// boolean | "mixed"
		checked,
		// (event: React.ChangeEvent<HTMLInputElement>) => void
		onChange: (event) => setChecked(event.target.checked),
		// boolean
		disabled,
	});
	return (
		<div>
			<label>
				<input {...inputProps} ref={inputRef} />
				How about this cool example?
			</label>
			<button onClick={() => setChecked("mixed")}>Mix it up</button>
			<hr />
			Current state is: <pre>{String(stateData.checked)}</pre>
		</div>
	);
}
```

#### `useMixedCheckbox` signature

```ts
function useMixedCheckbox(
	ref: React.RefObject<HTMLInputElement | null>,
	args?: {
		checked?: boolean | "mixed";
		defaultChecked?: boolean;
		disabled?: boolean;
		onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
		onClick?: (event: React.MouseEvent<HTMLInputElement>) => void;
	},
	functionOrComponentName: string = "useMixedCheckbox"
): [
	{
		"aria-checked"?: boolean | "mixed";
		checked: boolean;
		disabled: boolean;
		onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
		onClick: (event: React.MouseEvent<HTMLInputElement>) => void;
		type: "checkbox";
	},
	{ checked: boolean | "mixed" }
];
```

### CustomCheckbox

A checkbox component with a wrapper element for custom styling.

#### CustomCheckbox CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-custom-checkbox] {
}
[data-reach-custom-checkbox][data-state="checked"] {
}
[data-reach-custom-checkbox][data-state="unchecked"] {
}
[data-reach-custom-checkbox][data-state="mixed"] {
}
```

#### CustomCheckbox Props

| Prop                                               | Type                         | Required |
| -------------------------------------------------- | ---------------------------- | -------- |
| [`span` props](#customcheckbox-span-props)         |                              |          |
| [`checked`](#customcheckbox-checked)               | `boolean` <Pipe /> `"mixed"` | false    |
| [`children`](#customcheckbox-children)             | `node` <Pipe /> `func`       | false    |
| [`defaultChecked`](#customcheckbox-defaultchecked) | `boolean`                    | false    |
| [`disabled`](#customcheckbox-disabled)             | `boolean`                    | false    |
| [`name`](#customcheckbox-name)                     | `string`                     | false    |
| [`onChange`](#customcheckbox-onchange)             | `func`                       | false    |
| [`value`](#customcheckbox-value)                   | `string \| number`           | false    |

##### CustomCheckbox `span` props

All props are spread to an underlying `span` element.

```jsx
<CustomCheckbox className="cool-checkbox" />
```

##### CustomCheckbox `checked`

`checked?: boolean | "mixed"`

Whether or not the checkbox is checked or in a `mixed` (indeterminate) state.

##### CustomCheckbox `children`

`children?: React.ReactNode;`

A `CustomCheckbox` can accept any React node as children so long as the rendered content is valid HTML. It is best to avoid adding interactive elements inside of a `CustomCheckbox`

```jsx
function Example({ innerStyle, ...props }) {
  return (
    <span>
      <label>
        <CustomCheckbox {...props}>
          <span aria-hidden style={...innerStyle} />
        </CustomCheckbox>
      </label>
    </span>
  );
}
```

##### CustomCheckbox `defaultChecked`

`defaultChecked?: boolean`

For uncontrolled checkbox components, `defaultChecked` dictates whether or not the default initial state for a checkbox is `checked`.

Because any checkbox with a `mixed` state must be controlled by the app, `defaultChecked` only accepts `true` or `false` values.

##### CustomCheckbox `disabled`

`disabled?: boolean`

Whether or not the checkbox form input is disabled.

##### CustomCheckbox `name`

`name?: string`

The `name` attribute passed to the checkbox form input.

##### CustomCheckbox `onChange`

`onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;`

The callback that is fired when the checkbox value is changed.

##### CustomCheckbox `value`

`value?: string | number`

The `value` attribute passed to the checkbox form input.

### CustomCheckboxContainer

Wrapper component and context provider for a custom checkbox. It should be used in conjunction with the `CustomCheckboxInput`.

#### CustomCheckboxContainer CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-custom-checkbox-container] {
}
[data-reach-custom-checkbox-container][data-state="checked"] {
}
[data-reach-custom-checkbox-container][data-state="unchecked"] {
}
[data-reach-custom-checkbox-container][data-state="mixed"] {
}
```

#### CustomCheckboxContainer Props

| Prop                                                        | Type                         | Required |
| ----------------------------------------------------------- | ---------------------------- | -------- |
| [`span` props](#customcheckboxcontainer-span-props)         |                              |          |
| [`checked`](#customcheckboxcontainer-checked)               | `boolean` <Pipe /> `"mixed"` | false    |
| [`children`](#customcheckboxcontainer-children)             | `node` <Pipe /> `func`       | true     |
| [`defaultChecked`](#customcheckboxcontainer-defaultchecked) | `boolean`                    | false    |
| [`disabled`](#customcheckboxcontainer-disabled)             | `boolean`                    | false    |
| [`onChange`](#customcheckboxcontainer-onchange)             | `func`                       | false    |

##### CustomCheckboxContainer `span` props

All props are spread to an underlying `span` element.

```jsx
<CustomCheckboxContainer className="cool-checkbox">
	<CustomCheckboxInput />
</CustomCheckboxContainer>
```

##### CustomCheckboxContainer `checked`

`checked?: boolean | "mixed"`

Same as [`CustomCheckbox` `checked`](#customcheckbox-checked).

This prop is assigned to the `CustomCheckboxContainer` and passed to the `CustomCheckboxInput` via the [React Context API](https://reactjs.org/docs/context.html).

##### CustomCheckboxContainer `children`

`children: React.ReactNode | ((args: { checked: boolean | "mixed", inputRef: React.RefObject, focused: boolean }) => JSX.Element)`

A `CustomCheckboxContainer` can accept a React node or render prop function as its child. It should always have one `CustomCheckboxInput` component as a descendant.

```jsx
function Example({ children, name, value, id, label, ...props }) {
	return (
		<span>
			<CustomCheckbox {...props}>
				{({ checked, focused }) => (
					<span
						aria-hidden
						style={{
							display: "block",
							outline: focused ? "2px solid aqua" : undefined,
						}}
					>
						{checked === "mixed" ? "⛔" : checked ? "✅" : "❌"}
						<CustomCheckboxInput id={id} name={name} value={value} />
					</span>
				)}
			</CustomCheckbox>
			<label htmlFor={id}>{label}</label>
		</span>
	);
}
```

##### CustomCheckboxContainer `defaultChecked`

`defaultChecked?: boolean`

Same as [`CustomCheckbox` `defaultChecked`](#customcheckbox-defaultchecked).

This prop is assigned to the `CustomCheckboxContainer` and passed to the `CustomCheckboxInput` via the [React Context API](https://reactjs.org/docs/context.html).

##### CustomCheckboxContainer disabled

`disabled?: boolean`

Same as [`CustomCheckbox` `disabled`](#customcheckbox-disabled).

This prop is assigned to the `CustomCheckboxContainer` and passed to the `CustomCheckboxInput` via the [React Context API](https://reactjs.org/docs/context.html).

##### CustomCheckboxContainer onChange

`onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;`

Same as [`CustomCheckbox` `onChange`](#customcheckbox-onchange).

This prop is assigned to the `CustomCheckboxContainer` and passed to the `CustomCheckboxInput` via the [React Context API](https://reactjs.org/docs/context.html).

### CustomCheckboxInput

Component to render the HTML input element for our custom checkbox. The rendered element should be visually hidden and exists only to manage its state and hold a form name and value.

#### CustomCheckboxInput CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-custom-checkbox-input] {
}
[data-reach-custom-checkbox-input][aria-checked="true"] {
}
[data-reach-custom-checkbox-input][aria-checked="false"] {
}
[data-reach-custom-checkbox-input][aria-checked="mixed"] {
}
```

#### CustomCheckboxInput Props

`CustomCheckboxInput` inherits its props from the `React.ComponentProps<'input'>` type, excluding:

- [`checked`](#customcheckboxcontainer-checked)
- [`defaultChecked`](#customcheckboxcontainer-defaultchecked)
- [`disabled`](#customcheckboxcontainer-disabled)
- [`onChange`](#customcheckboxcontainer-onchange)`

Each of these props, if needed, should instead be assigned to `CustomCheckboxContainer`. They are passed to the `CustomCheckboxInput` via the [React Context API](https://reactjs.org/docs/context.html)

[Check out the React documentation for additional information about form inputs in React.](https://reactjs.org/docs/forms.html)
